// 1、let声明的变量只有在他所在的代码块有效
{
  let a = 10
  var b = 1
}
a // ReferenceError: a is not defined
b // 1

// 2、let声明的计时器i在循环体外引用就会报错
for (let i = 0; i < 10; i++) {
  // ...
}
console.log(i) // ReferenceError: i is not defined

// 3、var声明的计时器i，在全局范围内有效
var a = []
for (var i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i)
  }
}
a[6]() // 10

// 4、let声明的变量仅在块级作用域有效
let a = []
for (let i = 0; i < 10; i++) {
  a[i] = function () {
    console.log(i)
  }
}
a[6]() // 6

// 5、let声明的函数内部变量i与循环变量i不在同一个作用域
for (let i = 0; i < 3; i++) {
  let i = 'abc'
  console.log(i)
}
// abc
// abc
// abc

// 不存在变量提升
// var 的情况
console.log(foo) // undefined
var foo = 2

// let 的情况
console.log(bar) // ReferenceError
let bar = 2

// 暂时性死区
if (true) {
  // TDZ开始
  tmp = 'abc' // ReferenceError
  console.log(tmp) // ReferenceError

  let tmp
  console.log(tmp)

  tmp = 123;
  console.log(tmp)
}

// 暂时性死区意味着`typeof`不再是一个百分之百安全的操作
typeof x // ReferenceError
let x

// 作为比较，如果一个变量根本没有被声明，使用`typeof`反而不会报错
typeof undeclared_variable // "undefined"

// 隐蔽的死区
function bar(x = y, y = 2) {
  return [x, y]
}
bar() // 报错

function bar(x = 2, y = x) {
  return [x, y]
}
bar() // [2, 2]

// 
var x = x

let x = x // ReferenceError: x is not defined

// 不允许重复声明
// 不允许在相同作用域内，重复声明同一个变量
function func() {
  let a = 10
  var a = 1
}

function func() {
  let a = 10
  let a = 1 // 报错
}

// 在函数内部重新声明参数
function func(arg) {
  let arg // 报错
}

function func(arg) {
  {
    let arg // 正确
  }
}

// 内层变量可能你会覆盖外层变量
// es5
var tmp = new Date()

function f() {
  console.log(tmp)
  if (false) {
    var tmp = 'hello world'
  }
  // 原因在于变量提升
  /**
   * function f() {
   *   var tmp
   *   console.log(tmp)
   *   if (false) {
   *      tmp = 'hello world'
   *    }
   * }
   */
}
f() // undefined

// 用来计数的循环变量泄漏为全局变量
var s = 'hello'
for (var i = 0; i < s.length; i++) {
  console.log(s[i])
}
console.log(i) // 5

// ES6的块级作用域
// let实际上为JavaScript新增了块级作用域
function f1() {
  let n = 5
  if (true) {
    let n = 10
  }
  console.log(n) // 5
}

// ES6允许块级作用域的任意嵌套，外层无法读到内层变量
{
  {
    {
      {
        {
          let insane = 'Hello World'
        }
        // console.log(insane) // 报错
      }
    }
  }
}

// 内层作用域可以定义外层作用域的同名变量
{
  {
    {
      {
        let insane = 'Hello World'
        // {let insane = 'Hello World'}
      }
    }
  }
}

// 取代了立即执行函数表达式(IIFE)
// IIFE写法
(function () {
  var tmp = ''
  // ...
}())

// 块级作用域写法
{
  let tmp = ''
}

// 2 - 3. 块级作用域与函数声明
// ES5规定，函数不能在块级作用域中声明
// 非法示例,但是浏览器为兼容不报错
if (true) {
  function f() {}
}
try {
  function f() {}
} catch (e) {}

// ##### 3. const命令
// 3 - 1. 基本用法
// - `const`声明一个只读常量，并不可修改
const PI = 3.1415
PI // 3.1415

PI = 3 // TypeError: Assignment to constant variable.

// - 一旦声明就必须初始化
const foo // SyntaxError: Missing initializer in const declaration

// - `const` 作用域与 `let`命令相同
if (true) {
  const MAX = 5
}
MAX // Uncaught ReferenceError: MAX is not defined

// - 存在暂时性死区
if (true) {
  console.log(MAX)
  const MAX = 5
}
// - 不可重复声明
var message = 'Helle'
let age = 25

const message = 'Goodbye'
const age = 30

// 3 - 2. 本质
// > 变量指向的那个内存地址所保存的数据不得改动。即基础类型不变，复杂类型指向的数据结构可变
const foo = {}
// 可添加属性
foo.prop = 123
foo.prop // 123
// 将foo指向另一个对象报错
foo = {} // TypeError: "foo" is read-only

const a = []
a.push['hello'] // 执行
a.length = 0 // 执行
a = ['Dave'] // 报错

// - 冻结对象，`Object.freeze`
const foo = Object.freeze({})
// 常规模式时，下面一行不起作用；
// 严格模式时，该行会报错
foo.prop = 123

// - 对象本身冻结，对象属性叶冻结
var constantize = (obj) => {
  Object.freeze(obj)
  Object.keys(obj).forEach((key, i) => {
    if (typeof obj[key] === 'object') {
      constantize(obj[key])
    }
  })
}
// 3 - 3. ES6
// #### 4. 顶层对象的属性
// > ES5中顶层对象window（浏览器）和global（Node）的属性与全局变量是等价的
window.a = 1
a // 1
a = 2
window.a // 2

// > ES6种let、const、class命令声明的对象是全局对象，并不属于顶层对象
var a = 1
window.a // 1

let b = 1
window.b // underfined

// #### 5. global对象
// 5 - 1. 两种勉强取到顶层对象的方法
// - 方法一
`(typeof window !== 'undefined' ?
  window :
  (typeof process === 'object' &&
    typeof require === 'function' &&
    typeof global === 'object') ?
  global :
  this)`
// - 方法二 
var getGlobal = function () {
  if (typeof self !== 'undefined') {
    return self
  }
  if (typeof window !== 'undefined') {
    return window
  }
  if (typeof global !== 'undefined') {
    return global
  }
  throw new Error('unable to locate global object')
}

// 5 - 2. `system.global` 模拟 `global`作为顶层对象的提案
// CommonJS写法
require('system.global/shim')()
var global = require('system.global')()

// ES6写法
import shim from 'system.global/shim'
shim()

import getGlobal from 'system.global'
const global = getGlobal()